package org.go.plugin.self;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.go.domain.GoDomain;
import org.go.expcetion.GoException;
import org.go.expcetion.JobPersistenceException;
import org.go.expcetion.SchedulerConfigException;
import org.go.expcetion.SchedulerException;
import org.go.log.Log;
import org.go.log.LoggerFactory;
import org.go.plugin.GoPlugin;
import org.go.scheduler.Scheduler;
import org.go.spi.ClassLoadHelper;
import org.go.spi.impl.CascadingClassLoadHelper;
import org.go.trigger.Trigger;
import org.go.trigger.TriggerKey;
import org.go.work.Work;
import org.go.xml.JobFile;
import org.go.xml.XMLSchedulingDataProcessor;

/**
 * 
 * @author hejie
 *
 */
public class XmlWorkerDataPlugin implements GoPlugin {
	private static Log log = LoggerFactory.getLog(XmlWorkerDataPlugin.class);
	public static final int REPEAT_INDEFINITELY = -1;
	private boolean failOnFileNotFound = true;
	private Map<String, JobFile> jobFiles = new LinkedHashMap<String, JobFile>();
	private Set<String> jobTriggerNameSet = new HashSet<String>();
	private String name;
	private long scanInterval = 0;
	private Scheduler scheduler;
	//private List<Scheduler> schedulers;
	protected ClassLoadHelper classLoadHelper = null;
	protected List<String> jobGroupsToDelete = new LinkedList<String>();
	protected List<String> jobGroupsToNeverDelete = new LinkedList<String>();
	boolean started = false;

	private String buildJobTriggerName(String fileBasename) {
		// Name w/o collisions will be prefix + _ + filename (with '.' of filename replaced with '_')
		// For example: JobInitializationPlugin_jobInitializer_myjobs_xml
		String jobTriggerName = GoDomain.JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName() + '_' + fileBasename.replace('.', '_');

		// If name is too long (DB column is 80 chars), then truncate to max length
		if (jobTriggerName.length() > GoDomain.MAX_JOB_TRIGGER_NAME_LEN) {
			jobTriggerName = jobTriggerName.substring(0, GoDomain.MAX_JOB_TRIGGER_NAME_LEN);
		}

		// Make sure this name is unique in case the same file name under different
		// directories is being checked, or had a naming collision due to length truncation.
		// If there is a conflict, keep incrementing a _# suffix on the name (being sure
		// not to get too long), until we find a unique name.
		int currentIndex = 1;
		while (jobTriggerNameSet.add(jobTriggerName) == false) {
			// If not our first time through, then strip off old numeric suffix
			if (currentIndex > 1) {
				jobTriggerName = jobTriggerName.substring(0, jobTriggerName.lastIndexOf('_'));
			}

			String numericSuffix = "_" + currentIndex++;

			// If the numeric suffix would make the name too long, then make room for it.
			if (jobTriggerName.length() > (GoDomain.MAX_JOB_TRIGGER_NAME_LEN - numericSuffix.length())) {
				jobTriggerName = jobTriggerName.substring(0, (GoDomain.MAX_JOB_TRIGGER_NAME_LEN - numericSuffix.length()));
			}

			jobTriggerName += numericSuffix;
		}

		return jobTriggerName;
	}

	private void processFile(JobFile jobFile) {
		if (jobFile == null) {
			return;
		}
		try {
			XMLSchedulingDataProcessor processor = new XMLSchedulingDataProcessor(classLoadHelper);
			processor.addJobGroupToNeverDelete(GoDomain.JOB_INITIALIZATION_PLUGIN_NAME);
			processor.addTriggerGroupToNeverDelete(GoDomain.JOB_INITIALIZATION_PLUGIN_NAME);
			processor.processFileAndScheduleJobs(jobFile.getFileName(), jobFile.getFileName(), scheduler);
		} catch (JobPersistenceException e) {
			log.error("Error scheduling workers: " + e.getMessage(), e);
		} catch (Exception e) {
			log.error("Error scheduling workers: " + e.getMessage(), e);
		}
	}

	public String getName() {
		return name;
	}

	public Scheduler getScheduler() {
		return scheduler;
	}

	@Override
	public void initialize(String name, Scheduler sche) throws SchedulerConfigException {
		this.scheduler = sche;
		this.classLoadHelper = new CascadingClassLoadHelper();
		this.classLoadHelper.initialize();

		log.info("Registering GO Worker Initialization Plug-in.");

		// Create JobFile objects
		StringTokenizer stok = new StringTokenizer(GoDomain.FILE_NAMES, GoDomain.FILE_NAME_DELIMITERS);
		while (stok.hasMoreTokens()) {
			final String fileName = stok.nextToken();
			JobFile jobFile;
			try {
				jobFile = new JobFile(fileName, classLoadHelper);
				jobFiles.put(fileName, jobFile);
			} catch (GoException e) {
				e.printStackTrace();
			}
		}
	}

	public boolean isFailOnFileNotFound() {
		return failOnFileNotFound;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setScheduler(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

	@Override
	public void shutdown() {

	}

	@Override
	public void start() {
		try {
			if (jobFiles.isEmpty() == false) {
				if (scanInterval > 0) {
					//getScheduler().getContext().put(GoDomain.JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName(), this);
				}
				Iterator<JobFile> iterator = jobFiles.values().iterator();
				while (iterator.hasNext()) {
					JobFile jobFile = iterator.next();
					// scan job_data.xml autoly per serveral seconds 
					if (scanInterval > 0L) {
						String jobTriggerName = buildJobTriggerName(jobFile.getFileBasename());
						TriggerKey tKey = new TriggerKey(jobTriggerName, GoDomain.JOB_INITIALIZATION_PLUGIN_NAME);
						// remove pre-existing job/trigger, if any
						//getScheduler().unscheduleJob(tKey);
						// TODO: convert to use builder
						Trigger trig = getScheduler().getTrigger(tKey);
						trig = null;
						//new RepeatTrigger();
						//						trig.setName(jobTriggerName);
						//						trig.setGroup(GoDomain.JOB_INITIALIZATION_PLUGIN_NAME);
						//						trig.setStartTime(new Date());
						//						trig.setEndTime(null);
						//						trig.setRepeatCount(REPEAT_INDEFINITELY);
						//						trig.setRepeatInterval(scanInterval);
						// TODO: convert to use builder
						Work job = null;
						//new Work(jobTriggerName, GoDomain.JOB_INITIALIZATION_PLUGIN_NAME, FileScanWork.class);

						//job.getJobDataMap().put(FileScanJob.FILE_NAME, jobFile.getFileName());
						//job.getJobDataMap().put(FileScanJob.FILE_SCAN_LISTENER_NAME, JOB_INITIALIZATION_PLUGIN_NAME + '_' + getName());
						//
						getScheduler().scheduler(job, trig);
						log.debug("Scheduled file scan job for data file: {}, at interval: {}", jobFile.getFileName(), scanInterval);
					}
					processFile(jobFile);
				}
			}
		} catch (SchedulerException e1) {
			e1.printStackTrace();
		} catch (Exception e1) {
			log.error("{}", e1);
		} finally {
			started = true;
		}
	}
}
